package com.mchain.api;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;

import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.protocol.http.HttpService;
import org.web3j.protocol.parity.methods.response.NewAccountIdentifier;
import org.web3j.utils.Convert;

import com.mchain.parity.MyParity;
import com.mchain.parity.MyTransaction;
import com.mchain.parity.response.BooleanResponse;
import com.mchain.pojo.Account;
import com.mchain.pojo.BatchCreateAccountsResp;
import com.mchain.pojo.Error;
import com.mchain.pojo.TransactionResp;
import com.mchain.utils.PasswordGenerator;

/**
 * Created by leo on 2017/7/12.
 */
public class ParityWrapper {

	private final MyParity myParity;

	public ParityWrapper(String url) {
		myParity = new MyParity(new HttpService(url));
	}

	/**
	 * batch create num accounts
	 * 
	 * @param num
	 * @return
	 */
	public BatchCreateAccountsResp batchCreateAccount(int num) {
		return batchCreateAccount(num, null);
	}

	/**
	 * batch create accounts for each account name
	 * 
	 * @param accountNames
	 * @return
	 */
	public BatchCreateAccountsResp batchCreateAccount(List<String> accountNames) {
		return batchCreateAccount(accountNames.size(), accountNames);
	}

	private BatchCreateAccountsResp batchCreateAccount(int num, List<String> accountNames) {
		BatchCreateAccountsResp batchResp = new BatchCreateAccountsResp(num);

		if (accountNames != null && num != accountNames.size()) {
			// never go there
			batchResp.setLastError(new Error(Error.BAD_REQUEST_ID, Error.BAD_REQUEST_MSG));
			return batchResp;
		}

		try {
			for (int i = 0; i < num; i++) {
				String password = PasswordGenerator.createPassWord(8);
				NewAccountIdentifier newAccountIdentifier = myParity.personalNewAccount(password).send();
				if (newAccountIdentifier == null || newAccountIdentifier.hasError()) {
					// TODO should record log
					batchResp.setLastError(new Error(newAccountIdentifier.getError()));
					batchResp.fail();
					continue;
				}

				String accountId = newAccountIdentifier.getAccountId();
				Account account = new Account();
				account.setAccoundId(accountId);
				account.setPassword(password);
				batchResp.addAccount(account);

				if (accountNames != null) {
					String name = accountNames.get(i);
					BooleanResponse boolResp = myParity.paritySetAccountName(accountId, name).send();
					if (boolResp == null || boolResp.hasError()) {
						// event failed to set account name, the account also be
						// created
						batchResp.setLastError(new Error(newAccountIdentifier.getError()));
					}
				}
				batchResp.success();
			}
		} catch (Exception e) {
			e.printStackTrace();
			Error err = new Error(Error.JAVA_EXCEPTION_ID, Error.JAVA_EXCEPTION_MSG);
			err.setData(e.getMessage());
			batchResp.setLastError(err);
		}

		return batchResp;
	}

	public BigDecimal getBalance(String accountID, boolean mustSync) {
		return getBalance(accountID, Convert.Unit.ETHER, mustSync);
	}

	public BigDecimal getBalance(String accountID, Convert.Unit unit, boolean mustSync) {
		try {
			if (mustSync) {
				EthSyncing ethSync = myParity.ethSyncing().send();
				if (ethSync == null || ethSync.hasError()) {
					return null;
				}
				if (!ethSync.isSyncing()) {
					return null;
				}
			}

			EthGetBalance ethBalance = myParity.ethGetBalance(accountID, DefaultBlockParameterName.LATEST).send();
			if (ethBalance == null || ethBalance.hasError()) {
				// Should log
			}
			return Convert.fromWei(new BigDecimal(ethBalance.getBalance()), unit);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			return null;
		}

	}

	public TransactionResp trans(String accountId, String passsword, String toAccountId, BigInteger amount,
			BigInteger gasPrice, BigInteger gasLimit, String data, BigInteger afterBlockNum, Long time) {
		try {
			EthGetTransactionCount ethGetTransactionCount = myParity.ethGetTransactionCount(accountId, DefaultBlockParameterName.LATEST).sendAsync().get();
			if (ethGetTransactionCount.hasError()) {
				Error e = new Error(Error.COMMON_ERROR_ID, Error.COMMON_ERROR_MSG);
				e.setData("faild to get transaction count, msg: " + ethGetTransactionCount.getError().getMessage());
				return new TransactionResp(e) ;
			}

			BigInteger nonce = ethGetTransactionCount.getTransactionCount();
			
			BigInteger bTime = null;
			if (time != null) {
				bTime = new BigInteger(String.valueOf(time)); 
			}
			MyTransaction trans = new MyTransaction(accountId, nonce, gasPrice, gasLimit, toAccountId, amount, data,
					afterBlockNum, bTime);
			EthSendTransaction sendResult = myParity.personalSendTransaction(trans, passsword).send();
			if (sendResult.hasError()) {
				Error e = new Error(Error.COMMON_ERROR_ID, Error.COMMON_ERROR_MSG);
				e.setData("faild to do transaction count, msg: " + sendResult.getError().getMessage());
				return new TransactionResp(e) ;
			} 
			return new TransactionResp(sendResult.getTransactionHash());
		} catch (Exception e) {
			Error ee = new Error(Error.COMMON_ERROR_ID, Error.COMMON_ERROR_MSG);
			ee.setData("faild to do transaction, msg: " + e.getMessage());
			return new TransactionResp(ee) ;
		} 
	}

	//0:not been confirmed, pending status
	//>0:been confirmed by how many blocks untill current block
	//-1:orphan block
	public int getTransactionConfirmedClainLenth(String txHash)
	{
		try {
			EthTransaction trans = myParity.ethGetTransactionByHash(txHash).send();
			if(trans.hasError() || !trans.getTransaction().isPresent())
			{
				throw new RuntimeException("ethGetTransactionByHash() get error:"+trans.getError());
			}
			Transaction thransaction = trans.getTransaction().get();
			String blockHash=thransaction.getBlockHash();
			BigInteger blockNumber=thransaction.getBlockNumber();
            if(blockHash==null || blockNumber==null)
			{
				return 0;
			}

			//get the  block which  transaction confirmed
			EthBlock ethBlock = myParity.ethGetBlockByNumber(new DefaultBlockParameterNumber(blockNumber),true).send();
			if (ethBlock.hasError()) {
				throw new RuntimeException("ethGetBlockByNumber:"+ blockNumber+" get error:"+ethBlock.getError());
			}
			EthBlock.Block block = ethBlock.getBlock();
			if(!blockHash.equals(block.getHash()))
			{
				return -1;
			}
			//get current block
			EthBlock currentEthBlock = myParity.ethGetBlockByNumber(DefaultBlockParameterName.LATEST,true).send();
			if (ethBlock.hasError()) {
				throw new RuntimeException("ethGetBlockByNumber:"+ "Latest"+" get error:"+ethBlock.getError());
			}
			BigInteger currentBlockNum = ethBlock.getBlock().getNumber();
			BigInteger blockClain = currentBlockNum.subtract(blockNumber);
			return blockClain.intValue();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}
